package huu.phong.googlemusic;

import huu.phong.googlemusic.model.Song;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;

public class MediaPlayer extends Thread{
	private Player player;
	private Thread thread, buffer;
	private GoogleMusic main;
	private byte[] bytes = new byte[12000000];
	private InputStream in;
	int offset = 0, numRead = -1, index = -1, length;
	private boolean toPlay = false;
    Song currentSong;
    private Configure configure;
    private int duration = 0;
    private Header header;
    private String bitrate;
    int actualDuration = 0;
    private int currentDuration = 0;
    int startSize = -1;
    int plusDuration = 0;
    boolean dragged = false;
    boolean paused = false;
    boolean buffered = false;
    boolean allowBuffer = false;
    private URL url;
    private Random random;
    boolean bufferringFlag = false;
    List<Integer> selectedIndex = new ArrayList<Integer>();
    int numberReconnect = 0;
    int numberRecently = 0;
    boolean playing = false;
    URLConnection connection;
    long startTime = 0;
    
	public MediaPlayer(GoogleMusic main, Configure configure) {
		this.main = main;
		this.configure = configure;
		this.start();
		random = new Random();
		init();
	}
	
	public void play(int indexSong) throws MalformedURLException, JavaLayerException, InterruptedException{
		toPlay = false;
		allowBuffer = false;
		if (player != null) player.close();
		main.setRange(0);
		playing = true;
		main.setPlay();
		reset();
		selectedIndex.add(indexSong);
		currentSong = configure.songs.get(indexSong);
		main.setCurrentSong(indexSong);
		main.setTitle("[GETING] [" + currentSong + "] [" + configure.title + "]");
		main.setIconStatus(true);
		main.songInfo.setText(" [GETING] [" + currentSong + "] [" + configure.title + "]");
		if (selectedIndex.size() > 1){
			System.err.println("Exit");
			return;
		}
		if (bufferringFlag){
			System.err.println("Buffering Flag: " + bufferringFlag);
			System.err.println("Allow buffer: " + allowBuffer);
			synchronized (buffer) {
				buffer.wait();
			}
		}
		bufferringFlag = true;
		main.itemSaveCurrentSong.setEnabled(false);
		numberReconnect = 0;
		do {
			try {
				index = selectedIndex.get(selectedIndex.size() - 1);
				url = new URL(currentSong.getDirectLink());
				if (index != selectedIndex.get(selectedIndex.size() - 1)) {
					continue;
		        }
				connection = url.openConnection();
				length = connection.getContentLength();
				in = connection.getInputStream();
			} catch (IOException e) {
				main.setTitle(e.toString());
				e.printStackTrace();
				if(++numberReconnect <= configure.numberReconnect){
					System.err.println("Reconnect " + numberReconnect);
					main.setTitle("[RECONNECT] [" + currentSong + "] [" + configure.title + "]");
					index = -1;
					continue;
				}
				allowBuffer = false;
				bufferringFlag = false;
				selectedIndex.clear();
				main.setTitle("[NETWORK ERROR] [" + configure.title + "]");
				main.setIconStatus(false);
		        main.songInfo.setText(configure.status);
				return;
			}
			if (length > bytes.length) bytes = new byte[length];
			main.setTitle("[BUFFERING] [" + currentSong + "] [" + configure.title + "]");
			offset = 0;
	        numRead = -1;
	        header = null;
	        buffered = false;
	        System.err.println("Playing index: " + index + ":" + (selectedIndex.get(selectedIndex.size() - 1)));
		} while(index != selectedIndex.get(selectedIndex.size() - 1));
		selectedIndex.clear();
        allowBuffer = true;
        main.setIconStatus(false);
        main.songInfo.setText(currentSong.getSongInfo());
	}
	
	public void init() {
		thread = new Thread() {
			public void run() {
				while (true) {
					try {
						if (toPlay) {
							System.err.println(startSize);
							if (startSize == -1) plusDuration = 0;
							player = new Player(new ByteArrayInputStream(bytes, (startSize = startSize == -1 ? 0 : startSize), length - startSize));
							startSize = -1;
							playing = true;
							main.setPlay();
							player.play();
							if (configure.repeat.equals("One song") || startSize > -1){
								System.err.println("Repeat");
								continue;
							}
							System.err.println("BUFFERED " + buffered);
							if (toPlay){
								next();
							}else{
								playing = false;
								if (!paused) main.songInfo.setText(configure.status);
							}
						} else {
							Thread.sleep(1000);
						}
					}catch (IOException e) {
						main.setTitle(e.toString());
						e.printStackTrace();
						if (!selectedIndex.isEmpty()) index = selectedIndex.get(selectedIndex.size() - 1);
						main.setCurrentSong(index);
						selectedIndex.clear();
						bufferringFlag = false;
						main.songInfo.setText(configure.status);
					}catch (InterruptedException e) {
						main.setTitle(e.toString());
						e.printStackTrace();
					}catch (JavaLayerException e) {
						main.setTitle(e.toString());
						e.printStackTrace();
						seek(0);
					}
				}
			}
		};
		thread.start();
		buffer = new Thread(){
        	public void run(){
    			while (true){
    				while (allowBuffer){
    					try {
							if (offset < length && in != null && (numRead = in.read(bytes, offset, length - offset)) >= 0){
								offset += numRead;
							    if (header == null && offset >= 2048) {
							    	Bitstream bitstream = new Bitstream(new ByteArrayInputStream(bytes));
							    	header = bitstream.readFrame();
							    	bitstream.close();
							    	if (header == null) continue; 
							    	duration = getMP3Duration(length);
							    	main.setMax(duration);
							    	main.endDuration.setText(toDuaration(duration) + "  ");
							    	bitrate = (header.vbr()? "VBR": "CBR") + " | " + header.bitrate_string() + " | " + header.sample_frequency_string() + " | " + header.mode_string();
							    	main.setTitle("[BUFFERING] " + currentSong + " [" + bitrate + " | 00:00 / " + toDuaration(duration) + "] [" + configure.title + "]");
								    }
							    	actualDuration = getMP3Duration(offset);
							    	if (allowBuffer) main.setRange(actualDuration);
								    if (actualDuration > configure.buffered && allowBuffer && !paused) {
								    	toPlay = true;
								    }
								}else{
									try{
										if (in != null) in.close();
								        if(++numberReconnect <= configure.numberReconnect && offset < length && allowBuffer){
								        	System.err.println("Reconnect " + numberReconnect);
								        	System.err.println("Allow buffer " + allowBuffer);
								        	connection = url.openConnection();
								        	connection.setRequestProperty("Accept-Ranges", "bytes");
											connection.setRequestProperty("Range", "bytes=" + offset + "-");
											connection.connect();
//											System.err.println(connection.getHeaderField(0));
								        	in = connection.getInputStream();
								        	continue;
								        }else if (offset == length){
								        	buffered  = true;
								        	main.itemSaveCurrentSong.setEnabled(true);
								        }else if (numberReconnect >= configure.numberReconnect){
								        	main.setTitle("[NETWORK ERROR] [" + configure.title + "]");
								        }
								        allowBuffer = false;
								        System.err.println("close buffer");
								        System.err.println(offset);
								        System.err.println(length);
									}catch (IOException e) {
										main.setTitle(e.toString());
										e.printStackTrace();
										in = null;
										if (allowBuffer) continue;
									}
								}
						} catch (BitstreamException e) {
							main.setTitle(e.toString());
							e.printStackTrace();
						} catch (IOException e) {
							main.setTitle(e.toString());
							e.printStackTrace();
						}
        			}
    				bufferringFlag = false;
    				synchronized (buffer) {
    					buffer.notify();
					}
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						main.setTitle(e.toString());
						e.printStackTrace();
					}
    			}
        	}
        };
        buffer.start();
	}

	public void pause() {
		toPlay = false;
		if (player != null) player.close();
		paused = true;
		playing = false;
		seek(currentDuration);
		System.out.println(currentDuration);
	}
	
	public void resumeSong(){
		toPlay = true;
		paused = false;
		playing = true;
	}
	
	public void stopSong(){
		toPlay = false;
		if (player != null) player.close();
		allowBuffer = false;
		main.setTitle(configure.title);
		paused = false;
		reset();
	}
	
	public String toDuaration(int position){
		int min = position / 60000;
		int sec = (position / 1000) % 60;
		return numberToString(min) + ":" + numberToString(sec);
	}
	
	private String numberToString(int number){
		String ret = String.valueOf(number);
		while (ret.length() < 2){
			ret = "0" + ret;
		}
		return ret;
	}
	
	public void run(){
		while (true){
			try {
				if (toPlay && player != null && !player.isComplete()){
					currentDuration = player.getPosition() + plusDuration;
					if (!buffered && (currentDuration + 1500) > actualDuration){
						System.err.println("Buffer lower");
						seek(0);
						continue;
					}
					main.setTitle(currentSong + " [" + bitrate + " | " + toDuaration(currentDuration) + " / " + toDuaration(duration) + "] [" + configure.title + "]");
					main.startDuration.setText("  " + toDuaration(currentDuration));
					if (!dragged) main.setValue(currentDuration);
				}
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				main.setTitle(e.toString());
				e.printStackTrace();
			}
		}
	}
	
	public void next() throws MalformedURLException, IOException, JavaLayerException, InterruptedException{
		if (configure.repeat.equals("Random")){
			index = random.nextInt(configure.songs.size());
			play(index);
			return;
		}
		if (index + 1 >= configure.songs.size()){
			if (configure.repeat.equals("All song")){
				play(index = 0);
				return;
			}else{
				toPlay = false;
				return;
			}
		}
		play(++index);
	}
	
	public void previous() throws MalformedURLException, IOException, JavaLayerException, InterruptedException{
		System.err.println(index);
		if (index <= 0) return;
		play(--index);
	}
	
	public int getMP3Duration(int length){
		if (header == null) return 0;
        return (int)header.total_ms(length);
	}
	
	public int durationToSize(int duration){
		return (int) ((duration/1000.0 * length) /(this.duration / 1000.0));
	}
	
	public void reset(){
		main.endDuration.setText("00:00");
		main.startDuration.setText("00:00");
		main.setValue(0);
		main.setRange(0);
	}

	public void seek(int value){
		plusDuration = value;
		startSize = durationToSize(value);
		if (player != null) player.close();
	}

	public void saveFile(String file) {
		try{
			BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
			out.write(bytes, 0, length);
			out.flush();
			out.close();
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
}
